# 高通QSDK nl80211框架 该文章介绍了应用层通过NL80211的libnl库,和底层驱动交互的过程。并以高通wifitool和cfg80211tool 运行机制进行举例说明。 ## NL80211机制 应用层使用nl消息发送NL80211_CMD_xx到驱动层,驱动根据NL80211_CMD_xx,找到对应的ops调用。在高通QSDK中,像wifitool、cfg80211tool、wpa_supplicant等很多工具都是基于nl消息。如下图所示: ![](media/image-20240222135018351.png) 套路如下 1. 创建socket和内核联系 ```c #define WIFI_NL80211_CMD_SOCK_ID DEFAULT_NL80211_CMD_SOCK_ID //777 #define WIFI_NL80211_EVENT_SOCK_ID DEFAULT_NL80211_EVENT_SOCK_ID //778 struct socket_context sock_ctx; sock_ctx.cfg80211 = 1; init_socket_context(&sock_ctx, WIFI_NL80211_CMD_SOCK_ID, WIFI_NL80211_EVENT_SOCK_ID); ``` 2. 首先创建nl消息,携带NL80211_CMD_xx,默认的命令如下`qca/src/linux-4.4/user_headers/include/linux/nl80211.h/enum nl80211_commands` ```c enum nl80211_commands { /* don't change the order or add anything between, this is ABI! */ NL80211_CMD_UNSPEC, NL80211_CMD_GET_WIPHY, /* can dump */ NL80211_CMD_SET_WIPHY, NL80211_CMD_NEW_WIPHY, NL80211_CMD_DEL_WIPHY, NL80211_CMD_GET_INTERFACE, /* can dump */ NL80211_CMD_SET_INTERFACE, NL80211_CMD_NEW_INTERFACE, NL80211_CMD_DEL_INTERFACE, NL80211_CMD_GET_KEY, NL80211_CMD_SET_KEY, NL80211_CMD_NEW_KEY, NL80211_CMD_DEL_KEY, NL80211_CMD_GET_BEACON, NL80211_CMD_SET_BEACON, NL80211_CMD_START_AP, NL80211_CMD_NEW_BEACON = NL80211_CMD_START_AP, NL80211_CMD_STOP_AP, NL80211_CMD_DEL_BEACON = NL80211_CMD_STOP_AP, NL80211_CMD_GET_STATION, NL80211_CMD_SET_STATION, NL80211_CMD_NEW_STATION, NL80211_CMD_DEL_STATION, NL80211_CMD_GET_MPATH, NL80211_CMD_SET_MPATH, NL80211_CMD_NEW_MPATH, NL80211_CMD_DEL_MPATH, NL80211_CMD_SET_BSS, NL80211_CMD_SET_REG, NL80211_CMD_REQ_SET_REG, NL80211_CMD_GET_MESH_CONFIG, NL80211_CMD_SET_MESH_CONFIG, NL80211_CMD_SET_MGMT_EXTRA_IE /* reserved; not used */, NL80211_CMD_GET_REG, NL80211_CMD_GET_SCAN, NL80211_CMD_TRIGGER_SCAN, NL80211_CMD_NEW_SCAN_RESULTS, NL80211_CMD_SCAN_ABORTED, NL80211_CMD_REG_CHANGE, NL80211_CMD_AUTHENTICATE, NL80211_CMD_ASSOCIATE, NL80211_CMD_DEAUTHENTICATE, NL80211_CMD_DISASSOCIATE, NL80211_CMD_MICHAEL_MIC_FAILURE, NL80211_CMD_REG_BEACON_HINT, NL80211_CMD_JOIN_IBSS, NL80211_CMD_LEAVE_IBSS, NL80211_CMD_TESTMODE, NL80211_CMD_CONNECT, NL80211_CMD_ROAM, NL80211_CMD_DISCONNECT, NL80211_CMD_SET_WIPHY_NETNS, NL80211_CMD_GET_SURVEY, NL80211_CMD_NEW_SURVEY_RESULTS, NL80211_CMD_SET_PMKSA, NL80211_CMD_DEL_PMKSA, NL80211_CMD_FLUSH_PMKSA, NL80211_CMD_REMAIN_ON_CHANNEL, NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL, NL80211_CMD_SET_TX_BITRATE_MASK, NL80211_CMD_REGISTER_FRAME, NL80211_CMD_REGISTER_ACTION = NL80211_CMD_REGISTER_FRAME, NL80211_CMD_FRAME, NL80211_CMD_ACTION = NL80211_CMD_FRAME, NL80211_CMD_FRAME_TX_STATUS, NL80211_CMD_ACTION_TX_STATUS = NL80211_CMD_FRAME_TX_STATUS, NL80211_CMD_SET_POWER_SAVE, NL80211_CMD_GET_POWER_SAVE, NL80211_CMD_SET_CQM, NL80211_CMD_NOTIFY_CQM, NL80211_CMD_SET_CHANNEL, NL80211_CMD_SET_WDS_PEER, NL80211_CMD_FRAME_WAIT_CANCEL, NL80211_CMD_JOIN_MESH, NL80211_CMD_LEAVE_MESH, NL80211_CMD_UNPROT_DEAUTHENTICATE, NL80211_CMD_UNPROT_DISASSOCIATE, NL80211_CMD_NEW_PEER_CANDIDATE, NL80211_CMD_GET_WOWLAN, NL80211_CMD_SET_WOWLAN, NL80211_CMD_START_SCHED_SCAN, NL80211_CMD_STOP_SCHED_SCAN, NL80211_CMD_SCHED_SCAN_RESULTS, NL80211_CMD_SCHED_SCAN_STOPPED, NL80211_CMD_SET_REKEY_OFFLOAD, NL80211_CMD_PMKSA_CANDIDATE, NL80211_CMD_TDLS_OPER, NL80211_CMD_TDLS_MGMT, NL80211_CMD_UNEXPECTED_FRAME, NL80211_CMD_PROBE_CLIENT, NL80211_CMD_REGISTER_BEACONS, NL80211_CMD_UNEXPECTED_4ADDR_FRAME, NL80211_CMD_SET_NOACK_MAP, NL80211_CMD_CH_SWITCH_NOTIFY, NL80211_CMD_START_P2P_DEVICE, NL80211_CMD_STOP_P2P_DEVICE, NL80211_CMD_CONN_FAILED, NL80211_CMD_SET_MCAST_RATE, NL80211_CMD_SET_MAC_ACL, NL80211_CMD_RADAR_DETECT, NL80211_CMD_GET_PROTOCOL_FEATURES, NL80211_CMD_UPDATE_FT_IES, NL80211_CMD_FT_EVENT, NL80211_CMD_CRIT_PROTOCOL_START, NL80211_CMD_CRIT_PROTOCOL_STOP, NL80211_CMD_GET_COALESCE, NL80211_CMD_SET_COALESCE, NL80211_CMD_CHANNEL_SWITCH, NL80211_CMD_VENDOR, NL80211_CMD_SET_QOS_MAP, NL80211_CMD_ADD_TX_TS, NL80211_CMD_DEL_TX_TS, NL80211_CMD_GET_MPP, NL80211_CMD_JOIN_OCB, NL80211_CMD_LEAVE_OCB, NL80211_CMD_CH_SWITCH_STARTED_NOTIFY, NL80211_CMD_TDLS_CHANNEL_SWITCH, NL80211_CMD_TDLS_CANCEL_CHANNEL_SWITCH, NL80211_CMD_WIPHY_REG_CHANGE, NL80211_CMD_ABORT_SCAN, NL80211_CMD_START_NAN, NL80211_CMD_STOP_NAN, NL80211_CMD_ADD_NAN_FUNCTION, NL80211_CMD_DEL_NAN_FUNCTION, NL80211_CMD_CHANGE_NAN_CONFIG, NL80211_CMD_NAN_MATCH, NL80211_CMD_SET_MULTICAST_TO_UNICAST, NL80211_CMD_UPDATE_CONNECT_PARAMS, NL80211_CMD_SET_PMK, NL80211_CMD_DEL_PMK, NL80211_CMD_PORT_AUTHORIZED, NL80211_CMD_RELOAD_REGDB, NL80211_CMD_EXTERNAL_AUTH, NL80211_CMD_STA_OPMODE_CHANGED, NL80211_CMD_CONTROL_PORT_FRAME, NL80211_CMD_GET_FTM_RESPONDER_STATS, NL80211_CMD_PEER_MEASUREMENT_START, NL80211_CMD_PEER_MEASUREMENT_RESULT, NL80211_CMD_PEER_MEASUREMENT_COMPLETE, NL80211_CMD_NOTIFY_RADAR, NL80211_CMD_UPDATE_OWE_INFO, NL80211_CMD_PROBE_MESH_LINK, NL80211_CMD_SET_FILS_AAD, /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ __NL80211_CMD_AFTER_LAST, NL80211_CMD_MAX = __NL80211_CMD_AFTER_LAST - 1 }; ``` 这些命令对应的ops如下`qca/src/linux-4.4/net/wireless/nl80211.c/`: ```c static const struct genl_ops nl80211_ops[] = { { .cmd = NL80211_CMD_GET_WIPHY, .doit = nl80211_get_wiphy, .dumpit = nl80211_dump_wiphy, .done = nl80211_dump_wiphy_done, .policy = nl80211_policy, /* can be retrieved by unprivileged users */ .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_WIPHY, .doit = nl80211_set_wiphy, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_INTERFACE, .doit = nl80211_get_interface, .dumpit = nl80211_dump_interface, .policy = nl80211_policy, /* can be retrieved by unprivileged users */ .internal_flags = NL80211_FLAG_NEED_WDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_INTERFACE, .doit = nl80211_set_interface, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_NEW_INTERFACE, .doit = nl80211_new_interface, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_DEL_INTERFACE, .doit = nl80211_del_interface, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_KEY, .doit = nl80211_get_key, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_KEY, .doit = nl80211_set_key, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL | NL80211_FLAG_CLEAR_SKB, }, { .cmd = NL80211_CMD_NEW_KEY, .doit = nl80211_new_key, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL | NL80211_FLAG_CLEAR_SKB, }, { .cmd = NL80211_CMD_DEL_KEY, .doit = nl80211_del_key, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_BEACON, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .doit = nl80211_set_beacon, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_START_AP, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .doit = nl80211_start_ap, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_STOP_AP, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .doit = nl80211_stop_ap, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_STATION, .doit = nl80211_get_station, .dumpit = nl80211_dump_station, .policy = nl80211_policy, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_STATION, .doit = nl80211_set_station, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_NEW_STATION, .doit = nl80211_new_station, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_DEL_STATION, .doit = nl80211_del_station, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_MPATH, .doit = nl80211_get_mpath, .dumpit = nl80211_dump_mpath, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_MPP, .doit = nl80211_get_mpp, .dumpit = nl80211_dump_mpp, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_MPATH, .doit = nl80211_set_mpath, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_NEW_MPATH, .doit = nl80211_new_mpath, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_DEL_MPATH, .doit = nl80211_del_mpath, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_BSS, .doit = nl80211_set_bss, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_REG, .doit = nl80211_get_reg_do, .dumpit = nl80211_get_reg_dump, .policy = nl80211_policy, .internal_flags = NL80211_FLAG_NEED_RTNL, /* can be retrieved by unprivileged users */ }, #ifdef CONFIG_CFG80211_CRDA_SUPPORT { .cmd = NL80211_CMD_SET_REG, .doit = nl80211_set_reg, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_RTNL, }, #endif { .cmd = NL80211_CMD_REQ_SET_REG, .doit = nl80211_req_set_reg, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, }, { .cmd = NL80211_CMD_GET_MESH_CONFIG, .doit = nl80211_get_mesh_config, .policy = nl80211_policy, /* can be retrieved by unprivileged users */ .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_MESH_CONFIG, .doit = nl80211_update_mesh_config, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_TRIGGER_SCAN, .doit = nl80211_trigger_scan, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_ABORT_SCAN, .doit = nl80211_abort_scan, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_SCAN, .policy = nl80211_policy, .dumpit = nl80211_dump_scan, }, { .cmd = NL80211_CMD_START_SCHED_SCAN, .doit = nl80211_start_sched_scan, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_STOP_SCHED_SCAN, .doit = nl80211_stop_sched_scan, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_AUTHENTICATE, .doit = nl80211_authenticate, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL | NL80211_FLAG_CLEAR_SKB, }, { .cmd = NL80211_CMD_ASSOCIATE, .doit = nl80211_associate, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_DEAUTHENTICATE, .doit = nl80211_deauthenticate, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_DISASSOCIATE, .doit = nl80211_disassociate, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_JOIN_IBSS, .doit = nl80211_join_ibss, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_LEAVE_IBSS, .doit = nl80211_leave_ibss, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, #ifdef CONFIG_NL80211_TESTMODE { .cmd = NL80211_CMD_TESTMODE, .doit = nl80211_testmode_do, .dumpit = nl80211_testmode_dump, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, #endif { .cmd = NL80211_CMD_CONNECT, .doit = nl80211_connect, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_DISCONNECT, .doit = nl80211_disconnect, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_WIPHY_NETNS, .doit = nl80211_wiphy_netns, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_SURVEY, .policy = nl80211_policy, .dumpit = nl80211_dump_survey, }, { .cmd = NL80211_CMD_SET_PMKSA, .doit = nl80211_setdel_pmksa, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_DEL_PMKSA, .doit = nl80211_setdel_pmksa, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_FLUSH_PMKSA, .doit = nl80211_flush_pmksa, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_REMAIN_ON_CHANNEL, .doit = nl80211_remain_on_channel, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL, .doit = nl80211_cancel_remain_on_channel, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_TX_BITRATE_MASK, .doit = nl80211_set_tx_bitrate_mask, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_REGISTER_FRAME, .doit = nl80211_register_mgmt, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_FRAME, .doit = nl80211_tx_mgmt, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_FRAME_WAIT_CANCEL, .doit = nl80211_tx_mgmt_cancel_wait, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_POWER_SAVE, .doit = nl80211_set_power_save, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_POWER_SAVE, .doit = nl80211_get_power_save, .policy = nl80211_policy, /* can be retrieved by unprivileged users */ .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_CQM, .doit = nl80211_set_cqm, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_CHANNEL, .doit = nl80211_set_channel, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_WDS_PEER, .doit = nl80211_set_wds_peer, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_JOIN_MESH, .doit = nl80211_join_mesh, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_LEAVE_MESH, .doit = nl80211_leave_mesh, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_JOIN_OCB, .doit = nl80211_join_ocb, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_LEAVE_OCB, .doit = nl80211_leave_ocb, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, #ifdef CONFIG_PM { .cmd = NL80211_CMD_GET_WOWLAN, .doit = nl80211_get_wowlan, .policy = nl80211_policy, /* can be retrieved by unprivileged users */ .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_WOWLAN, .doit = nl80211_set_wowlan, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, #endif { .cmd = NL80211_CMD_SET_REKEY_OFFLOAD, .doit = nl80211_set_rekey_data, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL | NL80211_FLAG_CLEAR_SKB, }, { .cmd = NL80211_CMD_TDLS_MGMT, .doit = nl80211_tdls_mgmt, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_TDLS_OPER, .doit = nl80211_tdls_oper, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_UNEXPECTED_FRAME, .doit = nl80211_register_unexpected_frame, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_PROBE_CLIENT, .doit = nl80211_probe_client, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_REGISTER_BEACONS, .doit = nl80211_register_beacons, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_NOACK_MAP, .doit = nl80211_set_noack_map, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_START_P2P_DEVICE, .doit = nl80211_start_p2p_device, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_STOP_P2P_DEVICE, .doit = nl80211_stop_p2p_device, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_MCAST_RATE, .doit = nl80211_set_mcast_rate, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_MAC_ACL, .doit = nl80211_set_mac_acl, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_RADAR_DETECT, .doit = nl80211_start_radar_detection, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_PROTOCOL_FEATURES, .doit = nl80211_get_protocol_features, .policy = nl80211_policy, }, { .cmd = NL80211_CMD_UPDATE_FT_IES, .doit = nl80211_update_ft_ies, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_CRIT_PROTOCOL_START, .doit = nl80211_crit_protocol_start, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_CRIT_PROTOCOL_STOP, .doit = nl80211_crit_protocol_stop, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_GET_COALESCE, .doit = nl80211_get_coalesce, .policy = nl80211_policy, .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_COALESCE, .doit = nl80211_set_coalesce, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_CHANNEL_SWITCH, .doit = nl80211_channel_switch, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_VENDOR, .doit = nl80211_vendor_cmd, .dumpit = nl80211_vendor_cmd_dump, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_QOS_MAP, .doit = nl80211_set_qos_map, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_ADD_TX_TS, .doit = nl80211_add_tx_ts, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_DEL_TX_TS, .doit = nl80211_del_tx_ts, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_TDLS_CHANNEL_SWITCH, .doit = nl80211_tdls_channel_switch, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_TDLS_CANCEL_CHANNEL_SWITCH, .doit = nl80211_tdls_cancel_channel_switch, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_EXTERNAL_AUTH, .doit = nl80211_external_auth, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, { .cmd = NL80211_CMD_SET_FILS_AAD, .doit = nl80211_set_fils_aad, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_NETDEV_UP | NL80211_FLAG_NEED_RTNL, }, }; ``` 3. 当内核收到这些nl消息的时候就会调用该NL80211_CMD_xx对应的`.doit`函数; 4. `qca-wifi-g431c69b42e38-dirty/os/linux/tools/qcatools_lib.cqcatools_lib.c`就是对该过程的封装,编译成libqca_tools.so,提供api函数方便用户使用。 5. 下面以wifitool和cfg80211tool为例说明 ## wifitool ### 基本数据结构 1. wifi_cfg80211_context结构体 在cfg80211_nlwrapper_api.h文件中定义 ```c typedef struct wifi_cfg80211_t { /* command socket object */ struct nl_sock *cmd_sock; struct nl_sock *event_sock; /* private command socket ids*/ int pvt_cmd_sock_id; int pvt_event_sock_id; /* family id for 80211 driver */ int nl80211_family_id; void (*event_callback)(char *ifname, uint32_t subcmd, uint8_t *data,size_t len); pthread_t event_thread_handle; volatile int event_thread_running; } wifi_cfg80211_context; struct cfg80211_data { void *data; /* data pointer */ void *nl_vendordata; /* vendor data */ unsigned int nl_vendordata_len; /* vendor data length */ unsigned int length; /* data length */ unsigned int flags; /* flags for data */ unsigned int parse_data; /* 1 - data parsed by caller 0- data parsed by wrapper */ /* callback that needs to be called when data recevied from driver */ void (*callback) (struct cfg80211_data *); }; ``` 2. `struct socket_context`结构体 在`qca-wifi-g431c69b42e38-dirty/os/linux/tools/qcatools_lib.h` ```c struct socket_context { u_int8_t cfg80211; /* cfg80211 使能 flag */ #if UMAC_SUPPORT_CFG80211 wifi_cfg80211_context cfg80211_ctxt; /* cfg80211 context */ // 参见上面解释的 #endif int sock_fd; /* wext socket file descriptor */ }; ``` 3. todo ### 应用层连接内核nl sock 分析 ``` main init_socket_context wifi_init_nl80211 wifi_create_nl_socket wifi_socket_set_local_port nl_connect ``` 在`qca-wifi-g431c69b42e38-dirty/os/linux/tools/wifitool.c`中,main函数中创建socket和内核连接 ```c //定义全局 sock_ctx 是qca封装了一层 struct socket_context sock_ctx; int main(int argc, char *argv[]) { const char *ifname, *cmd; /* * Based on driver config mode (cfg80211/wext), application also runs * in same mode (wext?cfg80211) * 这里是cfg80211 故而sock_ctx.cfg80211=1 */ sock_ctx.cfg80211 = get_config_mode_type(); // 参数检查并偏移地址 if(streq(argv[argc-1],"-cfg80211") || streq(argv[argc-1],"--cfg80211")) { --argc; if (!sock_ctx.cfg80211){ fprintf(stderr, "Invalid tag 'cfg80211' for current mode.\n"); return -EINVAL; } sock_ctx.cfg80211 = 1; } // 得到 athx 和cmd ifname = argv[1]; cmd = argv[2]; // 下面这个先不管 #if UMAC_SUPPORT_CFG80211 if (sock_ctx.cfg80211 && streq(cmd, "setUnitTestCmdEvent")) { sock_ctx.cfg80211_ctxt.event_callback = cfg80211_event_callback; } #endif // 这个队列也是 libqca_tools.so提供,暂时先不解析 q_init(&event_q); // 初始化socket 和内核联系,这个函数下面分析 init_socket_context(&sock_ctx, WIFI_NL80211_CMD_SOCK_ID, WIFI_NL80211_EVENT_SOCK_ID); // 识别用户输入的字符串命令 调用对应的函数,这里肯定发送nl消息了 后面分析 if (streq(cmd, "rrmstats")) { get_rrmstats(ifname, argc, argv); } else if (streq(cmd, "get_rrmutil")) { get_rrmutil(ifname, argc, argv); } // 摧毁sock 释放资源 destroy_socket_context(&sock_ctx); return 0; } ``` #### init_socket_context 在`qca-wifi-g431c69b42e38-dirty/os/linux/tools/qcatools_lib.c`里面init_socket_context函数原型如下: ```c int init_socket_context (struct socket_context *sock_ctx, int cmd_sock_id, int event_sock_id) { int err = 0; #if UMAC_SUPPORT_CFG80211 if (sock_ctx->cfg80211) { // 记录用户传进来的私有id sock_ctx->cfg80211_ctxt.pvt_cmd_sock_id = cmd_sock_id; sock_ctx->cfg80211_ctxt.pvt_event_sock_id = event_sock_id; // 该函数位于qca-cfg80211-g431c69b42e38-dirty/qca_nl80211_lib/cfg80211_nlwrapper_api.c err = wifi_init_nl80211(&(sock_ctx->cfg80211_ctxt)); } #endif return 0; } ``` ##### wifi_init_nl80211 `qca-cfg80211-g431c69b42e38-dirty/qca_nl80211_lib/cfg80211_nlwrapper_api.c`下的wifi_init_nl80211函数原型如下 ```c int wifi_init_nl80211(wifi_cfg80211_context *ctx) { struct nl_sock *cmd_sock = NULL; nl80211_ctx = ctx; // 创建 nl_sock cmd_sock = wifi_create_nl_socket(ctx->pvt_cmd_sock_id, NETLINK_GENERIC); #define ALLOC_SIZE (256*1024) /* Set the socket buffer size */ nl_socket_set_buffer_size(cmd_sock, (ALLOC_SIZE), 0) ; // 保存sock句柄,和 nl80211_family_id 到这里才是 nl socket的 api 前面都是高通自己封装的 ctx->cmd_sock = cmd_sock; ctx->nl80211_family_id = genl_ctrl_resolve(cmd_sock, "nl80211"); if (setup_event_mechanism(ctx)) { goto cleanup; } return 0; cleanup: ctx->cmd_sock = NULL; nl_socket_free(cmd_sock); return -EIO; } ``` ###### wifi_create_nl_socket `qca-cfg80211-g431c69b42e38-dirty/qca_nl80211_lib/cfg80211_nlwrapper_api.c`下的wifi_create_nl_socket函数原型如下: ```c struct nl_sock *wifi_create_nl_socket(int port, int protocol) { struct nl_sock *sock = nl_socket_alloc(); // 设置sock 参数,该函数也在cfg80211_nlwrapper_api.c里面 wifi_socket_set_local_port(sock, port); // 连接sock if (nl_connect(sock, protocol)) { fprintf(stderr, "Could not connect handle\n"); nl_socket_free(sock); return NULL; } return sock; } ``` `wifi_socket_set_local_port`函数 ```c void wifi_socket_set_local_port(struct nl_sock *sock, uint32_t port) { #define PID_MASK 0x3FFFFF #define PORT_SHITF_VAL 22 uint32_t pid; pid = getpid() & PID_MASK; if (port == 0) { sock->s_flags &= ~NL_OWN_PORT; } else { sock->s_flags |= NL_OWN_PORT; } sock->s_local.nl_pid = pid + (port << PORT_SHITF_VAL); #undef PID_MASK #undef PORT_SHITF_VAL } ``` 自此,wifitool的nl socket和 内核连接起来了下面分析如何发送nl消息。 上面我们提到在main函数中,会根据用户输入的参数进行匹配,然后调用对应的处理函数发送nl消息,我们以`get_rssi函数`为例说明,nl消息到达内核的哪个层面 ### 应用层发送nl消息 get_rssi函数原型如下: ```c static int get_rssi(const char *ifname, int argc, char *argv[]) { struct ieee80211req_athdbg req = {0}; req.cmd = IEEE80211_DBGREQ_GETRRSSI; req.needs_reply = DBGREQ_REPLY_IS_NOT_REQUIRED; memset(req.dstmac,0x00,QDF_MAC_ADDR_SIZE); // 上面的req 就是缓存,里面格式怎么样 用户自己定义,到驱动层方便赋值而已,无需特别关注 // 需要注意下面函数的 QCA_NL80211_VENDOR_SUBCMD_DBGREQ 参数 // send_command(&sock_ctx, ifname, &req, sizeof(req), NULL, QCA_NL80211_VENDOR_SUBCMD_DBGREQ, IEEE80211_IOCTL_DBGREQ); return 0; } ``` #### send_command ```c int send_command (struct socket_context *sock_ctx, const char *ifname, void *buf, size_t buflen, void (*callback) (struct cfg80211_data *arg), int cmd, int ioctl_cmd) { #if UMAC_SUPPORT_CFG80211 int msg; struct cfg80211_data buffer; #endif if (sock_ctx->cfg80211) { #if UMAC_SUPPORT_CFG80211 buffer.data = buf; buffer.length = buflen; buffer.callback = callback; buffer.parse_data = 0; // 该函数用于创建nl消息,且QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION 和 cmd=QCA_NL80211_VENDOR_SUBCMD_DBGREQ msg = wifi_cfg80211_send_generic_command(&(sock_ctx->cfg80211_ctxt), QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION, cmd, ifname, (char *)&buffer, buflen); if (msg < 0) { printf("Could not send NL command\n"); return -1; } return buffer.length; #endif } return 0; } ``` ##### wifi_cfg80211_send_generic_command ```c int wifi_cfg80211_send_generic_command(wifi_cfg80211_context *ctx, int vendor_command, int cmdid, const char *ifname, char *buffer, int len) { // 此时的 vendor_command = QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION // cmdid = QCA_NL80211_VENDOR_SUBCMD_DBGREQ struct nl_msg *nlmsg = NULL; int res = -EIO; struct nlattr *nl_venData = NULL; struct cfg80211_data *cfgdata = (struct cfg80211_data *) buffer; // 这个函数会为 nl msg 分配空间 下面会分析 nlmsg = wifi_cfg80211_prepare_command(ctx, vendor_command, ifname); // vendor_command=QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION /* Prepare Actual Payload 1. nla_put - command ID. 2. nla_put - data 3. nla_put length QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND, QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_VALUE, QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA, QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_LENGTH, QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_FLAGS, */ if (nlmsg) { nl_venData = (struct nlattr *)start_vendor_data(nlmsg); if (!nl_venData) { fprintf(stderr, "failed to start vendor data\n"); nlmsg_free(nlmsg); return -EIO; } // 继续放数据 if (nla_put_u32(nlmsg, QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_LENGTH, len)) { fprintf(stderr, "\n Failed nla_put_u32, \n"); nlmsg_free(nlmsg); return -EIO; } if (nla_put_u32(nlmsg, QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND, cmdid)) { //QCA_NL80211_VENDOR_SUBCMD_DBGREQ nlmsg_free(nlmsg); return -EIO; } if (nla_put_u32(nlmsg, QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_FLAGS, cfgdata->flags)) { nlmsg_free(nlmsg); return -EIO; } if (nla_put(nlmsg, QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA, cfgdata->length, cfgdata->data)) { nlmsg_free(nlmsg); return -EIO; } else { if (nl_venData) { end_vendor_data(nlmsg, nl_venData); } // 发送消息 自此 结束 res = send_nlmsg(ctx, nlmsg, buffer); if (res < 0) { return res; } return res; } } else { return -EIO; } return res; } ``` ###### wifi_cfg80211_prepare_command 该函数会创建struct nl_msg消息命令为`NL80211_CMD_VENDOR`,并放入两个元素 - NL80211_ATTR_VENDOR_ID = QCA_VENDOR_OUI - NL80211_ATTR_VENDOR_SUBCMD = QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION =72 这里终于看到NL80211_CMD_VENDOR了。 ```c struct nl_msg *wifi_cfg80211_prepare_command(wifi_cfg80211_context *ctx, int cmdid, const char *ifname) { int res; // 分配空间 struct nl_msg *nlmsg = nlmsg_alloc(); genlmsg_put(nlmsg, 0, 0, ctx->nl80211_family_id,0, 0, NL80211_CMD_VENDOR, 0); // 放入 NL80211_ATTR_VENDOR_ID = QCA_VENDOR_OUI res = put_u32(nlmsg, NL80211_ATTR_VENDOR_ID, QCA_VENDOR_OUI); // 放入 NL80211_ATTR_VENDOR_SUBCMD = cmdid = QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION = 72 /* SET_WIFI_CONFIGURATION = 72 */ res = put_u32(nlmsg, NL80211_ATTR_VENDOR_SUBCMD, cmdid); set_iface_id(nlmsg, ifname); return nlmsg; } ``` **注意**:自此:经过上面的调用,我们得到nl消息里面存放内容如下:相当重要,后面驱动层会一点点取出来用 - NL80211_CMD_VENDOR - NL80211_ATTR_VENDOR_ID QCA_VENDOR_OUI - NL80211_ATTR_VENDOR_SUBCMD QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION - 下面这几个也是QCA 驱动层解析策略里面规定的 所有我们应用层都给填充了内容 - QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_LENGTH 用户传入buflen - QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND QCA_NL80211_VENDOR_SUBCMD_DBGREQ - QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_FLAGS cfgdata->flags - QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA cfgdata->data=上面我们创建的struct cfg80211_data cfgdata->length=其长度 重要的事情说三遍,这些nl 里面有什么内容,下面驱动层要用的。 #### 综上所述 应用层发送nl消息,命令为 NL80211_CMD_VENDOR 且子命令为 NL80211_ATTR_VENDOR_SUBCMD=QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION 根据章节[NL80211机制]介绍,NL80211_CMD_VENDOR命令对应 nl80211_vendor_cmd函数ops回调,在往下 就到了驱动层了 ```c static const struct genl_ops nl80211_ops[] = { // 此处省略了些,具体请看代码 { .cmd = NL80211_CMD_VENDOR, .doit = nl80211_vendor_cmd, .dumpit = nl80211_vendor_cmd_dump, .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, .internal_flags = NL80211_FLAG_NEED_WIPHY | NL80211_FLAG_NEED_RTNL, }, }; ``` ### 驱动层处理nl消息 #### nl80211_vendor_cmd函数解析 ```c static int nl80211_vendor_cmd(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; struct wireless_dev *wdev = __cfg80211_wdev_from_attrs(genl_info_net(info), info->attrs); int i, err; u32 vid, subcmd; // 取出 vid和 subcmd vid = nla_get_u32(info->attrs[NL80211_ATTR_VENDOR_ID]); //=QCA_VENDOR_OUI 注意这里的vid概念 下面会判断,如果不是这个vid 依然不会往下走 subcmd = nla_get_u32(info->attrs[NL80211_ATTR_VENDOR_SUBCMD]); // 注意这里 是应用层传过来的QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION for (i = 0; i < rdev->wiphy.n_vendor_commands; i++) { const struct wiphy_vendor_command *vcmd; void *data = NULL; int len = 0; // 该结构体注册的地方等下分析 vcmd = &rdev->wiphy.vendor_commands[i]; // 下面是合法性判断,先不用理会 //这里删掉了 // for循环 找到 nl 中的vid 和 subcmd 都和 注册的vcmd 里面匹配项,就可以往下执行, if (vcmd->info.vendor_id != vid || vcmd->info.subcmd != subcmd) continue; // 能到这里说明肯定找到了 下面 取出nl消息的data内容 if (info->attrs[NL80211_ATTR_VENDOR_DATA]) { data = nla_data(info->attrs[NL80211_ATTR_VENDOR_DATA]); // 此时就是用户传进来的空间 len = nla_len(info->attrs[NL80211_ATTR_VENDOR_DATA]); } rdev->cur_cmd_info = info; // 执行nl消息 的doit 钩子函数 此时对应的 err = rdev->wiphy.vendor_commands[i].doit(&rdev->wiphy, wdev, data, len); rdev->cur_cmd_info = NULL; return err; } return -EOPNOTSUPP; } ``` - 上述代码提到的`rdev->wiphy.vendor_commands`注册地方`qca-wifi-g431c69b42e38-dirty/offload/os/linux/ol_ath_linux.c/ol_ath_ifce_setup`函数 ```c void ol_ath_ifce_setup(struct ol_ath_softc_net80211 *scn, ifce_status ifce_up) { #if UMAC_SUPPORT_CFG80211 /* attach/detach both vap and radio specific handlers */ if (ic->ic_cfg80211_config) { if (ifce_up) { wlan_cfg80211_register_vendor_cmd_event_handlers(ic);// 在该函数中注册 } else { wlan_cfg80211_unregister_vendor_cmd_event_handlers(ic); } } #endif } ``` ```c void wlan_cfg80211_register_vendor_cmd_event_handlers(struct ieee80211com *ic) { ic->ic_wiphy->n_vendor_commands = ARRAY_SIZE(wlan_cfg80211_vendor_commands); ic->ic_wiphy->n_vendor_events = ARRAY_SIZE(wlan_cfg80211_vendor_events); ic->ic_wiphy->vendor_commands = wlan_cfg80211_vendor_commands; // 赋值的地方 ic->ic_wiphy->vendor_events = wlan_cfg80211_vendor_events; // 赋值的地方 } ``` - `qca-wifi-g431c69b42e38-dirty/os/linux/src/ieee80211_cfg80211.c/wlan_cfg80211_vendor_commands`结构体原型如下: ```c const struct wiphy_vendor_command wlan_cfg80211_vendor_commands[] = { { .info.vendor_id = QCA_NL80211_VENDOR_ID, .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wlan_cfg80211_set_wificonfiguration }, { .info.vendor_id = QCA_NL80211_VENDOR_ID, .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_GET_WIFI_CONFIGURATION, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wlan_cfg80211_get_wificonfiguration }, //此处省略了n行 }; ``` - 所以上面提到了子命令为QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION,故而 此时会调用wlan_cfg80211_set_wificonfiguration函数 ##### wlan_cfg80211_set_wificonfiguration qca-wifi-g431c69b42e38-dirty/os/linux/src/ieee80211_cfg80211.c文件中wlan_cfg80211_set_wificonfiguration函数原型如下: ```c static int wlan_cfg80211_set_wificonfiguration(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int data_len) { struct cfg80211_context *cfg_ctx = NULL; struct ieee80211com *ic = NULL; struct wlan_cfg8011_genric_params generic_params; int return_value = 0; int i; cfg_ctx = (struct cfg80211_context *)wiphy_priv(wiphy); ic = cfg_ctx->ic; qdf_mem_zero(&generic_params, sizeof(generic_params)); // 解析data 到 generic_params里面 此时的data 就是用户层传进来的NL80211_ATTR_VENDOR_DATA熟悉的空间 extract_generic_command_params(wiphy, data, data_len, &generic_params); switch(generic_params.command) { case QCA_NL80211_VENDOR_SUBCMD_WIFI_PARAMS: return_value = wlan_cfg80211_set_params(wiphy, wdev, &generic_params); break; case QCA_NL80211_VENDOR_SUBCMD_DBGREQ: // 会跑到这里 return_value = wlan_cfg80211_dbgreq(wiphy, wdev, generic_params.data, generic_params.data_len); break; default: qdf_print( "%s: Unsuported Genric command: %d ", __func__, generic_params.command); return_value = -EOPNOTSUPP; } return return_value; } ``` ###### extract_generic_command_params 解析data数据到params 中 ```c /* extract generic command params & send back them to caller */ static int extract_generic_command_params(struct wiphy *wiphy, const void *data, int data_len, struct wlan_cfg8011_genric_params *params) { struct cfg80211_context *cfg_ctx = NULL; struct ieee80211com *ic = NULL; struct nlattr *attr[QCA_WLAN_VENDOR_ATTR_CONFIG_MAX + 1]; cfg_ctx = (struct cfg80211_context *)wiphy_priv(wiphy); ic = cfg_ctx->ic; // 解析策略为wlan_cfg80211_set_wificonfiguration_policy /* static const struct nla_policy wlan_cfg80211_set_wificonfiguration_policy[QCA_WLAN_VENDOR_ATTR_CONFIG_MAX + 1] = { [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_VALUE] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA] = {.type = NLA_BINARY, .len = 5000 }, [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_LENGTH] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_FLAGS] = {.type = NLA_U32 }, }; */ if (wlan_cfg80211_nla_parse(attr, QCA_WLAN_VENDOR_ATTR_CONFIG_MAX, data, data_len, wlan_cfg80211_set_wificonfiguration_policy)) { return -EINVAL; } if (attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND]) { // 这里是 QCA_NL80211_VENDOR_SUBCMD_DBGREQ params->command = nla_get_u32( attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND]); } if (attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_VALUE]) { params->value = nla_get_u32( attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_VALUE]); } if (attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA]) { params->data = nla_data( attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA]); params->data_len = nla_len(attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA]); } if (attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_LENGTH]) { params->length = nla_get_u32( attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_LENGTH]); } if (attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_FLAGS]) { params->flags = nla_get_u32( attr[QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_FLAGS]); } return 0; } ``` 综上 会调用wlan_cfg80211_dbgreq函数 ```c static int wlan_cfg80211_dbgreq(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int data_len) { struct cfg80211_context *cfg_ctx = NULL; struct ieee80211com *ic = NULL; struct net_device *dev = NULL; osif_dev *osifp = NULL; wlan_if_t vap = NULL; int err = 0; struct ieee80211req_athdbg *req = (struct ieee80211req_athdbg *)data; cfg_ctx = (struct cfg80211_context *)wiphy_priv(wiphy); ic = cfg_ctx->ic; dev = wdev->netdev; osifp = ath_netdev_priv(dev); vap = osifp->os_if; if ((dev != NULL) && (req != NULL)){ // 到ucfg了 后面不在分析 err = ieee80211_ucfg_handle_dbgreq(dev, req, NULL, data_len); if (!err && req->needs_reply) { err = cfg80211_reply_command(wiphy, data_len, req, 0); } } return err; } ``` #### 综上 驱动层会根据应用层传递的nl 属性来解析,并调用相应的函数,结束整体的架构如下: ![](media/image-20240223100516589.png) ## cfg80211tool ### 工具编译过程 ![](media/image-20240131102734096.png) 如上图所示:在qca/feeds/qca/net/qca-wifi/Makefile中会把脚本 - `/cfg80211_configs/cfg80211tool` 安装到 `/usr/sbin` ![](media/image-20240131103100541.png) - `/cfg80211_configs/*.xml` 安装到 `/lib/wifi` - `/umac/son/cfg80211_configs/*.xml` 安装到 `/lib/wifi` 这个暂时不管 脚本`/cfg80211_configs/cfg80211tool`内如如下: ```bash #Copyright (c) 2016,2017 Qualcomm Innovation Center, Inc. #All Rights Reserved. #Confidential and Proprietary – Qualcomm Innovation Center, Inc #2016 Qualcomm Atheros, Inc. #All Rights Reserved. #Qualcomm Atheros Confidential and Proprietary. row=1 xml_path="" mesh_command="/usr/sbin/cfg80211tool_mesh" mesh_xml_path="/lib/wifi/qcacommands_mesh.xml" if [ "$0" == "$mesh_command" ] then xml_path="$mesh_xml_path" else xml_name=`sed -n "$row"p /sys/class/net/$1/cfg80211_xmlfile 2> /dev/null` if [ -n "$xml_name" ] then xml_path="/lib/wifi/$xml_name" fi fi if [ -z "$3" ] then val=0 else val=$3 fi # 这里用的的xml 在设备上存放路径为 /lib/wifi/qcacommands_vap.xml #echo "number of arguments: $# -> $0 $1 $2 $3 $4 $5" # Adjust command based on inputs if [ "$#" -eq 1 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h list fi if [ "$#" -eq 2 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --RESPONSE --$2 --END_CMD || return 255 fi if [ "$#" -eq 3 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --value0 $val --RESPONSE --$2 --END_CMD || return 255 fi if [ "$#" -eq 4 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --value0 $val --value1 $4 --RESPONSE --$2 --END_CMD || return 255 fi if [ "$#" -eq 5 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --value0 $val --value1 $4 --value2 $5 --RESPONSE --$2 --END_CMD || return 255 fi if [ "$#" -eq 6 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --value0 $val --value1 $4 --value2 $5 --value3 $6 --RESPONSE --$2 --END_CMD || return 255 fi if [ "$#" -eq 7 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --value0 $val --value1 $4 --value2 $5 --value3 $6 --value4 $7 --RESPONSE --$2 --END_CMD || return 255 fi if [ "$#" -eq 8 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --value0 $val --value1 $4 --value2 $5 --value3 $6 --value4 $7 --value5 $8 --RESPONSE --$2 --END_CMD || return 255 fi if [ "$#" -eq 9 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --value0 $val --value1 $4 --value2 $5 --value3 $6 --value4 $7 --value5 $8 --value6 $9 --RESPONSE --$2 --END_CMD || return 255 fi if [ "$#" -eq 10 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --value0 $val --value1 $4 --value2 $5 --value3 $6 --value4 $7 --value5 $8 --value6 $9 --value7 $10 --RESPONSE --$2 --END_CMD || return 255 fi ``` 从上面的内容可以看出,该脚本调用了`cfg80211tool.1` 工具,该bin文件生成过程如下: 软件包`qca/feeds/qca/utils/qca-cfg80211tool`下的Makefile内容如下,该软件包会生成 ```BASH include $(TOPDIR)/rules.mk PKG:=qca-cfg80211tool PKG_NAME:=$(PKG) PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=ssh://qca-git01.qualcomm.com:29418/wifi/$(PKG_NAME).git PKG_BRANCH:=master PKG_RELEASE:=1 LOCAL_SRC:=$(TOPDIR)/qca/src/common-tools DRIVER_PATH:=$(TOPDIR)/qca/src/qca-wifi include $(INCLUDE_DIR)/local-development.mk ifeq ($(DUMP)$(PKG_VERSION),) PKG_REV:=$(shell git ls-remote $(PKG_SOURCE_URL) $(PKG_BRANCH) | cut -b -7) PKG_VERSION:=g$(PKG_REV) endif PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG)-$(PKG_VERSION) include $(INCLUDE_DIR)/package.mk PKG_BUILD_DEPENDS:=qca-wifi define Package/$(PKG_NAME) SECTION:=QCA CATEGORY:=$(QTI_SOFTWARE_CATEGORY) URL:=http://www.qca.qualcomm.com MAINTAINER:=Qualcomm Atheros TITLE:= QCA cfg80211 utils DEPENDS:=+libnl +libpthread +libroxml +iw endef TARGET_CFLAGS += \ -I$(STAGING_DIR)/usr/include/ \ -I$(STAGING_DIR)/usr/include/libnl \ -I$(STAGING_DIR)/usr/include/libnl3 \ -D_GNU_SOURCE=1 -D__USE_GNU=1 -D__WIN__=1 -DCONFIG_SUPPORT_LIBROXML=1 TARGET_LDFLAGS += \ -lnl-3 -lnl-genl-3 -lroxml TARGET_CFLAGS += -fpie TARGET_CFLAGS += -fPICassociate/AAAAAAAAAAAAAAAAA TARGET_LDFLAGS += -pie define Package/$(PKG_NAME)/install $(INSTALL_DIR) $(1)/usr/sbin $(INSTALL_BIN) $(PKG_BUILD_DIR)/vendorcmdtool/cfg80211tool.1 $(1)/usr/sbin # 这里会将生成cfg80211tool.1拷贝到/usr/sbin下 endef $(eval $(call BuildPackage,$(PKG_NAME))) ``` 该软件包的源码位于`qca/src/common-tools/vendorcmdtool`下面,源码结构如下: ```bash . ├── common.h ├── LICENSE ├── Makefile ├── nl80211_copy.h ├── nl_cmd_wrapper.c #---根据命令 发送nl消息到底层驱动 ├── nl_cmd_wrapper.h ├── NOTICE ├── ven_cmd_tool.c #----解析xml └── ven_cmd_tool.h ``` Makefile内容如下: ```bash CC ?= gcc OBJS = ven_cmd_tool.o nl_cmd_wrapper.o CFLAGS += -Wall -Werror all: cfg80211tool.1 %.o: %.c $(CC) $(CFLAGS) $(LDFLAGS) -c -o $@ $< $(LDFLAGS) ifeq ($(CONFIG_SUPPORT_LIBROXML),1) cfg80211tool.1: $(OBJS) $(CC) $(LDFLAGS) -lroxml -o $@ $(OBJS) else cfg80211tool.1: $(OBJS) $(CC) $(LDFLAGS) -o $@ $(OBJS) endif clean: rm -rf *.o ``` 总结:当使用cfg80211tool时候,比如执行sendmgmt 发送控制帧的命令时候,实际上会调用 ``` cfg80211tool ath0 sendmgmt 000FFF01401100001800000040003c00000fff014011000fff010003000fff0140110000 ``` 实际调用是/usr/sbin/cfg80211tool.sh 脚本,然后脚本在根据输入的sendmgmt和 000FFF01401100001800000040003c00000fff014011000fff010003000fff0140110000这两个参数 ,调用cfg80211tool.1 bin工具 解析 /lib/wifi/qcacommands_vap.xml里面的内容, - 创建内核nl socket - 根据sendmgmt和/lib/wifi/qcacommands_vap.xml匹配 创建nl消息,把数据发送到驱动层,驱动层解析nl消息,然后执行对应的函数。 下面以`cfg80211tool sendmgmt` 发送管理帧为例说明: ``` cfg80211tool ath0 sendmgmt 000FFF01401100001800000040003c00000fff014011000fff010003000fff0140110000 ``` ### 应用层解析xml和输入参数 1. `/usr/sbin/cfg80211tool.sh ath0 sendmgmt 000FFF01401100001800000040003c00000fff014011000fff010003000fff0140110000` 该句在脚本/usr/sbin/cfg80211tool.sh中有三个参数 ```bash $1 = ath0 $xml_path = /lib/wifi/qcacommands_vap.xml $2 = sendmgmt $val = $3=000FFF01401100001800000040003c00000fff014011000fff010003000fff0140110000 if [ "$#" -eq 3 ] && [ -n "$xml_path" ] ; then cfg80211tool.1 -i $1 -f $xml_path -h none --START_CMD --$2 --value0 $val --RESPONSE --$2 --END_CMD || return 255 fi ``` 相当于下面的代码 ```bash val = 000FFF01401100001800000040003c00000fff014011000fff010003000fff0140110000 cfg80211tool.1 -i ath0 -f /lib/wifi/qcacommands_vap.xml -h none --START_CMD --sendmgmt --value0 $val --RESPONSE --sendmgmt --END_CMD || return 255 ``` 2. cfg80211tool.1程序 qca/src/common-tools/vendorcmdtool/ven_cmd_tool.c 下面的main函数如下: ``` int main(int argc, char **argv) { parseCmdinputs(info, argc, argv, cmds[NLMSG_TYPE_COMMAND],&option_index, data); { nlmsg = prepareNLMsg(info, command.attr_id[c], data.iface); { genlmsg_put(nlmsg, /* pid = */ 0, /* seq = */ 0, info->nl80211_family_id, 0, 0, NL80211_CMD_VENDOR, /* version = */ 0); res = nla_put(nlmsg, NL80211_ATTR_VENDOR_ID, sizeof(uint32_t), &OUI); res = nla_put(nlmsg, NL80211_ATTR_VENDOR_SUBCMD, sizeof(uint32_t), &cmdid); } nlVenData = startVendorData(nlmsg); //填充xml写入的数据到nl中 这里不错分析 // 发送消息 ret = sendNLMsg(info, nlmsg, &resp_info); } } ``` 经过上面对xml和用户输入参数的解析,现在msg中存放的消息内容如下: - NL80211_CMD_VENDOR - NL80211_ATTR_VENDOR_ID OUI - NL80211_ATTR_VENDOR_SUBCMD 从xml文件得到vendorCmd id值为74 对应QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION ![](media/image-20240222164933367.png) - QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND 248 - QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA 000FFF01401100001800000040003c00000fff014011000fff010003000fff0140110000 参见前面介绍的[extract_generic_command_params]章节介绍,nl消息解析策略如下: ```c static const struct nla_policy wlan_cfg80211_set_wificonfiguration_policy[QCA_WLAN_VENDOR_ATTR_CONFIG_MAX + 1] = { [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_VALUE] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA] = {.type = NLA_BINARY, .len = 5000 }, [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_LENGTH] = {.type = NLA_U32 }, [QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_FLAGS] = {.type = NLA_U32 }, }; ``` 所以驱动只支持策略中的5种格式, - 对应的ATTR宏定义如下: ![](media/image-20240223083701979.png) 所以xml中 ```xml # id=17 对应 QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND # id =19 对应QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA在, ``` 也就是在cfg80211tool.1程序中, - ID=17的 put QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_COMMAND nl消息中 - ID=19的 put QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA nl消息中 - 如果xml后面还有Attribute name,比如ID=18 - ID=18的put QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_VALUE nl消息中 - ID=20的put QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_LENGTH nl消息中 - ID=21的put QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_FLAGS nl消息中 **注意:**至于上面提到的TYPE="blob" 是cfg80211tool.1规定的,会把该类型的数据当做QCA_WLAN_VENDOR_ATTR_CONFIG_GENERIC_DATA放入nl消息中。 cfg80211tool.1工具中 一共支持以下数据类型 ![](media/image-20240223085558902.png) 至于这些类型如何放入nl消息的 参见下面的函数 ```c static status fillAttribute(struct cmd_params *cmd, int *c, struct nl_msg *nlmsg, int isdefault) { switch (cmd->data_type[*c]) { case FLAG: { if (nla_put_flag(nlmsg, cmd->attr_id[*c]) != SUCCESS) { printf("nla_put failed for attr: %d\n", cmd->attr_id[*c]); return MEM_NOT_AVAILABLE; } } break; case U8: { uint8_t value; if (isdefault) value = (uint8_t) strtoul(cmd->default_val[*c].val, NULL, 0); else value = (uint8_t) strtoul(optarg, NULL, 0); if (nla_put(nlmsg, cmd->attr_id[*c], sizeof(uint8_t), &value) != SUCCESS) { printf("nla_put failed for attr: %d, data : %u\n", cmd->attr_id[*c], value); return MEM_NOT_AVAILABLE; } } break; case U16: { uint16_t value; if (isdefault) value = (uint16_t) strtoul(cmd->default_val[*c].val, NULL, 0); else value = (uint16_t) strtoul(optarg, NULL, 0); if (nla_put(nlmsg, cmd->attr_id[*c], sizeof(uint16_t), &value)) { printf("nla_put failed for attr: %d, data : %u\n", cmd->attr_id[*c], value); return MEM_NOT_AVAILABLE; } } break; case U32: { uint32_t value; if (isdefault) value = (unsigned int ) strtoul(cmd->default_val[*c].val, NULL, 0); else value = (unsigned int ) strtoul(optarg, NULL, 0); if (nla_put(nlmsg, cmd->attr_id[*c], sizeof(uint32_t), &value)) { printf("nla_put failed for attr: %d, data : %u\n", cmd->attr_id[*c], value); return MEM_NOT_AVAILABLE; } } break; case U64: { uint64_t value; if (isdefault) value = (uint64_t) strtoul(cmd->default_val[*c].val, NULL, 0); else value = (uint64_t) strtoul(optarg, NULL, 0); //TODO: strtoul doesn't work for 64-bit types if (nla_put(nlmsg, cmd->attr_id[*c], sizeof(uint64_t), &value)) { printf("nla_put failed for attr: %d, data : %"PRIu64"\n", cmd->attr_id[*c], value); return MEM_NOT_AVAILABLE; } } break; case S8: { int8_t value; if (isdefault) value = (int8_t) strtoul(cmd->default_val[*c].val, NULL, 0); else value = (int8_t) strtoul(optarg, NULL, 0); if (nla_put(nlmsg, cmd->attr_id[*c], sizeof(int8_t), &value)) { printf("nla_put failed for attr: %d, data : %d\n", cmd->attr_id[*c], value); return MEM_NOT_AVAILABLE; } } break; case S16: { int16_t value; if (isdefault) value = (int16_t) strtoul(cmd->default_val[*c].val, NULL, 0); else value = (int16_t) strtoul(optarg, NULL, 0); if (nla_put(nlmsg, cmd->attr_id[*c], sizeof(int16_t), &value)) { printf("nla_put failed for attr: %d, data : %d\n", cmd->attr_id[*c], value); return MEM_NOT_AVAILABLE; } } break; case S32: { int32_t value; if (isdefault) value = (int32_t) strtoul(cmd->default_val[*c].val, NULL, 0); else value = (int32_t) strtoul(optarg, NULL, 0); if (nla_put(nlmsg, cmd->attr_id[*c], sizeof(int32_t), &value)) { printf("nla_put failed for attr: %d, data : %d\n", cmd->attr_id[*c], value); return MEM_NOT_AVAILABLE; } } break; case S64: { int64_t value; if (isdefault) value = (int64_t) strtoul(cmd->default_val[*c].val, NULL, 0); else value = (int64_t) strtoul(optarg, NULL, 0); //TODO: atoi doesn't work for 64-bit types if (nla_put(nlmsg, cmd->attr_id[*c], sizeof(int64_t), &value)) { printf("nla_put failed for attr: %d, data : %"PRId64"\n", cmd->attr_id[*c], value); return MEM_NOT_AVAILABLE; } } break; case STRING: { char *value; if (isdefault) value = cmd->default_val[*c].val; else value = optarg; if (nla_put(nlmsg, cmd->attr_id[*c], strlen(value), value)) { printf("nla_put failed for attr: %d, data : %s\n", cmd->attr_id[*c], optarg); return MEM_NOT_AVAILABLE; } } break; case BLOB: { char data[256]; if (string_to_hex(&data[0], 256, optarg, strlen(optarg)) != 0) { printf("Not able to parse hex data for attr: %d, data : %s\n", cmd->attr_id[*c], optarg); return INVALID_ARG; } if (nla_put(nlmsg, cmd->attr_id[*c], strlen(optarg)/2, data)) { printf("nla_put failed for attr: %d, data : %s\n", cmd->attr_id[*c], optarg); return MEM_NOT_AVAILABLE; } } break; case MAC_ADDR: { u8 mac_addr[MAC_ADDR_LEN]; char *value; if (isdefault) value = cmd->default_val[*c].val; else value = optarg; if (extract_mac_addr(mac_addr, value) == SUCCESS) { if (nla_put(nlmsg, cmd->attr_id[*c], 6, mac_addr)) { printf("nla_put failed for attr: %d, data : %s\n", cmd->attr_id[*c], value); return MEM_NOT_AVAILABLE; } } else { printf("Failed to parse mac address\n"); } } break; default: printf ("Uknown data type : %d\n", cmd->data_type[*c]); } return 0; } ``` 3. 消息发送后,到驱动层 ### 驱动层解析 到驱动层,NL80211_CMD_VENDOR对应的ops 然后根据[nl80211_vendor_cmd函数解析]介绍,会调用NL80211_ATTR_VENDOR_SUBCMD=QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION 对应的ops函数为wlan_cfg80211_set_wificonfiguration。 ```c const struct wiphy_vendor_command wlan_cfg80211_vendor_commands[] = { { .info.vendor_id = QCA_NL80211_VENDOR_ID, .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_SET_WIFI_CONFIGURATION, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wlan_cfg80211_set_wificonfiguration }, { .info.vendor_id = QCA_NL80211_VENDOR_ID, .info.subcmd = QCA_NL80211_VENDOR_SUBCMD_GET_WIFI_CONFIGURATION, .flags = WIPHY_VENDOR_CMD_NEED_WDEV | WIPHY_VENDOR_CMD_NEED_NETDEV, .doit = wlan_cfg80211_get_wificonfiguration }, //此处省略了n行 }; ``` 1. 在wlan_cfg80211_set_wificonfiguration函数中继续解析 ```c static int wlan_cfg80211_set_wificonfiguration(struct wiphy *wiphy, struct wireless_dev *wdev, const void *data, int data_len) { struct cfg80211_context *cfg_ctx = NULL; struct ieee80211com *ic = NULL; struct wlan_cfg8011_genric_params generic_params; int return_value = 0; int i; cfg_ctx = (struct cfg80211_context *)wiphy_priv(wiphy); ic = cfg_ctx->ic; qdf_mem_zero(&generic_params, sizeof(generic_params)); extract_generic_command_params(wiphy, data, data_len, &generic_params); // 解析策略分析过 /* call extract_generic_command_params */ switch(generic_params.command) { case QCA_NL80211_VENDORSUBCMD_SEND_MGMT: return_value = wlan_cfg80211_send_mgmt(wiphy, wdev, &generic_params); break; } return return_value; } ``` 2. extract_generic_command_params函数分析 参见前面介绍的[extract_generic_command_params]章节介绍 3. 后面的参见【高通QSDK 应用层发送控制帧.md/代码解析】 章节 ```c static int wlan_cfg80211_send_mgmt(struct wiphy *wiphy, struct wireless_dev *wdev, struct wlan_cfg8011_genric_params *params) { int ret = 0; struct ieee80211req_mgmtbuf *mgmt_frm; struct cfg80211_context *cfg_ctx = NULL; struct ieee80211com *ic = NULL; wlan_if_t vap = NULL; int cmd_type; void *cmd; uint32_t *data = (u_int32_t *) params->data; int i; cfg_ctx = (struct cfg80211_context *)wiphy_priv(wiphy); ic = cfg_ctx->ic; cmd = extract_command(ic, wdev, &cmd_type); if (cmd_type == VAP_CMD) { vap = (wlan_if_t)cmd; } else { qdf_err(" %s Command on invalid interface \n", __func__); return -EINVAL; } // 把000FFF0140110000 18000000 4000 3c00 000fff014011 000fff010003 000fff014011 0000 数据转换为ieee80211req_mgmtbuf类型的数据 /* * struct ieee80211req_mgmtbuf { * u_int8_t macaddr[IEEE80211_ADDR_LEN]; // 目的地址,虽然这里定义6字节,但是考虑到字节对齐,我们传递进来还是4字节对齐的000FFF0140110000 * u_int32_t buflen; // buf长度,这里定义为4字节,故而可以解释上面 0x18 在传递的时候是18000000 * u_int8_t buf[]; // buf[] = 4000 3c00 000fff014011 000fff010003 000fff014011 0000 * }; */ mgmt_frm = (struct ieee80211req_mgmtbuf *) params->data; /* WIM_LOG_DEBUG("SEND MGMT:start:mac:%s datalen:%d buflen:%d",ether_sprintf(mgmt_frm->macaddr),params->data_len,mgmt_frm->buflen); for(i=0;idata_len;i++){ printk("%02x ",((uint8_t*)params->data)[i]); } printk("\n"); */ if(mgmt_frm->buflen >= 37 && mgmt_frm->buf[24] == 0xff) { qdf_err(" %s unknown action frame\n", __func__); return -EINVAL; } /* if the macaddr requested is for broadcast then search for all connected sta and send the mgmt packet */ if(vap) { // 如果是广播的话,就跑到这里,我们上面传递的不是广播 if(IEEE80211_IS_BROADCAST(mgmt_frm->macaddr)) { ret = wlan_iterate_station_list(vap, wlan_cfg80211_sta_send_mgmt, mgmt_frm); } else { //故而走该分支,从这里可以看出,驱动是如何把数据解析为结构体 用的 ret = wlan_send_mgmt(vap, mgmt_frm->macaddr, mgmt_frm->buf, mgmt_frm->buflen); // 下面就是wlan_send_mgmt函数的问题了 ,这里不在分析 } } else { qdf_err("%s: Invalid vap \n", __func__); return -EINVAL; } /* wlan_iterate_station_list returns the number of sta connected, so return error only if the return value is less than zero */ if (ret < 0) { return ret; } else { return 0; } } ``` ### cfg80211tool参考文档 该部分是参考[[WLAN Driver CFG80211 User Guide](https://docs.qualcomm.com/bundle/80-YB478-3/resource/80-YB478-3_REV_E_WLAN_Driver_CFG80211_User_Guide.pdf)]文章